package ace.module.oauth2.server.core.impl.converter.impl;

import ace.cmp.core.constants.CoreConstant;
import ace.cmp.core.utils.SystemClockUtils;
import ace.module.oauth2.server.core.impl.converter.Oauth2AuthorizationFromOAuth2AuthorizationConverter;
import ace.module.oauth2.server.core.impl.converter.Oauth2ObjectJsonConverter;
import ace.module.oauth2.server.core.impl.dao.entity.Oauth2Authorization;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * @author caspar
 * @date 2024/2/21 16:18
 */
@AllArgsConstructor
@Component
public class Oauth2AuthorizationFromOAuth2AuthorizationConverterImpl
    implements Oauth2AuthorizationFromOAuth2AuthorizationConverter {
  private final Oauth2ObjectJsonConverter oAuth2ObjectJsonConverter;

  @Override
  public Oauth2Authorization convert(OAuth2Authorization source) {
    Oauth2Authorization.Oauth2AuthorizationBuilder builder = Oauth2Authorization.builder();

    builder
        .id(source.getId())
        .registeredClientId(source.getRegisteredClientId())
        .principalName(source.getPrincipalName())
        .authorizationGrantType(source.getAuthorizationGrantType().getValue());

    if (CollectionUtils.isNotEmpty(source.getAuthorizedScopes())) {
      builder.authorizationScopes(
          StringUtils.collectionToDelimitedString(source.getAuthorizedScopes(), ","));
    }

    String attributes = writeMap(source.getAttributes());
    builder.attributes(attributes);

    String state = null;
    String authorizationState = source.getAttribute(OAuth2ParameterNames.STATE);
    if (StringUtils.hasText(authorizationState)) {
      state = authorizationState;
    }
    builder.state(state);

    org.springframework.security.oauth2.server.authorization.OAuth2Authorization.Token<
            OAuth2AuthorizationCode>
        authorizationCode = source.getToken(OAuth2AuthorizationCode.class);
    TokenInfo oAuth2AuthorizationCodeTokenInfo = this.resolveTokenInfo(authorizationCode);
    builder
        .authorizationCodeValue(oAuth2AuthorizationCodeTokenInfo.getTokenValue())
        .authorizationCodeExpiresAt(oAuth2AuthorizationCodeTokenInfo.getTokenExpiresAt())
        .authorizationCodeIssuedAt(oAuth2AuthorizationCodeTokenInfo.getTokenIssuedAt())
        .authorizationCodeMetadata(oAuth2AuthorizationCodeTokenInfo.getTokenMetaData());

    org.springframework.security.oauth2.server.authorization.OAuth2Authorization.Token<
            OAuth2AccessToken>
        accessToken = source.getToken(OAuth2AccessToken.class);
    TokenInfo oAuth2AccessTokenTokenInfo = this.resolveTokenInfo(accessToken);

    String accessTokenType = null;
    String accessTokenScopes = null;
    if (accessToken != null) {
      accessTokenType = accessToken.getToken().getTokenType().getValue();
      if (!org.springframework.util.CollectionUtils.isEmpty(accessToken.getToken().getScopes())) {
        accessTokenScopes =
            StringUtils.collectionToDelimitedString(accessToken.getToken().getScopes(), ",");
      }
    }
    builder
        .accessTokenValue(oAuth2AccessTokenTokenInfo.getTokenValue())
        .accessTokenExpiresAt(oAuth2AccessTokenTokenInfo.getTokenExpiresAt())
        .accessTokenIssuedAt(oAuth2AccessTokenTokenInfo.getTokenIssuedAt())
        .accessTokenMetadata(oAuth2AccessTokenTokenInfo.getTokenMetaData())
        .accessTokenType(accessTokenType)
        .accessTokenScopes(accessTokenScopes);

    org.springframework.security.oauth2.server.authorization.OAuth2Authorization.Token<OidcIdToken>
        oidcIdToken = source.getToken(OidcIdToken.class);
    TokenInfo oidcIdTokenTokenInfo = this.resolveTokenInfo(oidcIdToken);
    builder
        .oidcIdTokenValue(oidcIdTokenTokenInfo.getTokenValue())
        .oidcIdTokenExpiresAt(oidcIdTokenTokenInfo.getTokenExpiresAt())
        .oidcIdTokenIssuedAt(oidcIdTokenTokenInfo.getTokenIssuedAt())
        .oidcIdTokenMetadata(oidcIdTokenTokenInfo.getTokenMetaData());

    org.springframework.security.oauth2.server.authorization.OAuth2Authorization.Token<
            OAuth2RefreshToken>
        refreshToken = source.getRefreshToken();
    TokenInfo refreshTokenTokenInfo = this.resolveTokenInfo(refreshToken);
    builder
        .refreshTokenValue(refreshTokenTokenInfo.getTokenValue())
        .refreshTokenExpiresAt(refreshTokenTokenInfo.getTokenExpiresAt())
        .refreshTokenIssuedAt(refreshTokenTokenInfo.getTokenIssuedAt())
        .refreshTokenMetadata(refreshTokenTokenInfo.getTokenMetaData());

    builder
        .createTime(SystemClockUtils.currentTimeMillis())
        .updateTime(SystemClockUtils.currentTimeMillis())
        .rowVersion(CoreConstant.DEFAULT_ROW_VERSION)
        .deleteFlag(CoreConstant.DELETE_FLAG_NOT.toString());

    return builder.build();
  }

  private String writeMap(Map<String, Object> data) {
    try {
      return this.oAuth2ObjectJsonConverter.toJson(data);
    } catch (Exception ex) {
      throw new IllegalArgumentException(ex.getMessage(), ex);
    }
  }

  private <T extends OAuth2Token> TokenInfo resolveTokenInfo(
      org.springframework.security.oauth2.server.authorization.OAuth2Authorization.Token<T> token) {
    String tokenValue = null;
    Long tokenIssuedAt = null;
    Long tokenExpiresAt = null;
    String metadata = null;
    if (token != null) {
      tokenValue = token.getToken().getTokenValue();
      if (token.getToken().getIssuedAt() != null) {
        tokenIssuedAt = token.getToken().getIssuedAt().toEpochMilli();
      }
      if (token.getToken().getExpiresAt() != null) {
        tokenExpiresAt = token.getToken().getExpiresAt().toEpochMilli();
      }
      metadata = writeMap(token.getMetadata());
    }

    return TokenInfo.builder()
        .tokenExpiresAt(tokenExpiresAt)
        .tokenIssuedAt(tokenIssuedAt)
        .tokenMetaData(metadata)
        .tokenValue(tokenValue)
        .build();
  }

  @Data
  @Builder
  @AllArgsConstructor
  @NoArgsConstructor
  private static class TokenInfo {
    private Long tokenIssuedAt;
    private Long tokenExpiresAt;
    private String tokenValue;
    private String tokenMetaData;
  }
}
